﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Faml.Frontend;
using System.Collections.Specialized;

namespace Faml.Optimization
{
    public class LambdaOptimizer 
    {
        private FamlExpressionContainer container;

        private LinkedList<FamlExpressionContainer> sequence;

        public LambdaOptimizer(FamlExpressionContainer container)
        {
            this.container = container;
            this.sequence = new LinkedList<FamlExpressionContainer>();
        }

        /// <summary>
        /// Gets an execution-ordered list of extracted expressions.
        /// </summary>
        public LinkedList<FamlExpressionContainer> OrderedExpressions
        {
            get
            {
                if (sequence.Count == 0)
                    throw new ApplicationException("LEO: call Optimize() first");

                return sequence;
            }
        }

        /// <summary>
        /// Performs a LEO procedure to extract sub-expressions those can be pre-evaluated.
        /// </summary>
        /// <returns>Returns true if optimization possibility was found and succesfully performed.</returns>
        public bool Optimize()
        {
            FamlExpression node = container.Expression;
            bool changed = Visit(container.Expression, out node);
            this.sequence.AddLast(container);

            if (changed)
            {
                container.Expression = node;                
                DebugResult();
            }            

            return changed;
        }

        [Conditional("DEBUG")]
        private void DebugResult()
        {
            System.Diagnostics.Debug.WriteLine(
                   string.Format("L-optimization for \"{0}\": {1} sub-expressions found",
                       container.Expression, sequence.Count));
                        
            StringBuilder sb = new StringBuilder("These are: ");
            foreach (var item in sequence)
            {
                sb.AppendFormat("\"{0}\", \n", item.Expression);
            }
            Debug.WriteLine(sb.ToString());
        }

        private bool Visit(FamlExpression node, out FamlExpression ne)
        {
            int i = 0;
            bool b = false;
            bool flag;
            FamlExpressionExtension ext = node.Metadata;

            // I am dependent
            if (!ext.IsIndependent) 
            {
                FamlExpression stub;
                switch (node.NodeType)
                {
                    case FamlExpressionType.FieldAccess:
                        FamlFieldExpression me = (FamlFieldExpression)node;

                        FamlExpression expr = me.Target;
                        if (expr.Metadata.IsIndependent)
                        {
                            stub = GetStub(expr);
                            if (stub != expr)
                            {
                                ne = new FamlFieldExpression(stub, me.Field); 
                                return true;
                            }
                        }
                        break;
                        
                    case FamlExpressionType.UnaryExpression:
                        FamlUnaryExpression ue = (FamlUnaryExpression)node;

                        FamlExpression operand = ue.Target;
                        if (operand.Metadata.IsIndependent)
                        {
                            stub = GetStub(operand);
                            if (stub != operand)
                            {
                                if (ue.Operator == UnaryOperations.Quote)
                                {
                                    ne = FamlUnaryExpression.MakeQuote(stub, ue.NodeParameters);
                                }
                                else
                                {
                                    ne = new FamlUnaryExpression(ue.Operator, stub);
                                }
                                return true;
                            }
                        }
                        break;


                    case FamlExpressionType.BinaryExpression:
                        FamlBinaryExpression be = (FamlBinaryExpression)node;

                        FamlExpression operandLeft = be.Left;
                        FamlExpression operandRight = be.Right;

                        b = false;
                        if (operandLeft.Metadata.IsIndependent)
                        {
                            operandLeft = GetStub(operandLeft);
                            b = operandLeft != be.Left;
                        }
                        else
                        {
                            b = Visit(operandLeft, out operandLeft) || b;
                        }

                        if (operandRight.Metadata.IsIndependent)
                        {
                            operandRight = GetStub(operandRight);
                            b = b || operandRight != be.Right;
                        }
                        else
                        {
                            b = Visit(operandRight, out operandRight) || b;
                        }

                        if (b)
                        {
                            ne = new FamlBinaryExpression(operandLeft, operandRight, be.Operator);
                            return true;
                        }
                        else
                        {
                            ne = node;
                            return false;
                        }


                    case FamlExpressionType.NewArray:
                        FamlInlineArrayExpression nae = (FamlInlineArrayExpression)node;

                        if (nae.Items.Count == 0)
                        {
                            ne = nae;
                            return false;
                        }

                        // TODO: possibly should add LEO inside inline arrays?
                        ne = nae;
                        return false;

                    case FamlExpressionType.ConditionalExpression:
                        FamlConditionalExpression fcnde = (FamlConditionalExpression)node;
                        FamlExpression cond = fcnde.Condition, trueBr = fcnde.TrueBranch, falseBr = fcnde.FalseBranch;

                        if (cond.Metadata.IsIndependent)
                        {
                            cond = GetStub(fcnde.Condition);
                            b = fcnde.Condition != cond;
                        }
                        else
                        {
                            b = Visit(fcnde.Condition, out cond) || b;
                        }
                        if (trueBr.Metadata.IsIndependent)
                        {
                            trueBr = GetStub(fcnde.TrueBranch);
                            b = fcnde.TrueBranch != trueBr;
                        }
                        else
                        {
                            b = Visit(fcnde.TrueBranch, out trueBr) || b;
                        }
                        if (falseBr.Metadata.IsIndependent)
                        {
                            falseBr = GetStub(fcnde.FalseBranch);
                            b = fcnde.FalseBranch != falseBr;
                        }
                        else
                        {
                            b = Visit(fcnde.FalseBranch, out falseBr) || b;
                        }
                        if (b)
                        {
                            ne = new FamlConditionalExpression(cond, trueBr, falseBr);
                            return true;
                        }
                        else
                        {
                            ne = node; return false;
                        }

                    case FamlExpressionType.Dictionary:
                        FamlDictionaryExpression fde = (FamlDictionaryExpression)node;

                        string[] keys = fde.GetKeys();
                        OrderedDictionary dict = new OrderedDictionary(keys.Length);

                        b = false;
                        FamlExpression exp, fe;
                        for (i = 0; i < keys.Length; i++)
                        {
                            exp = fde.GetField(keys[i]);

                            if (exp.Metadata.IsIndependent)
                            {
                                fe = GetStub(exp);
                                b = b || fe != exp;
                            }
                            else
                            {
                                b = Visit(exp, out fe) || b;
                            }
                            dict.Add(keys[i], fe);
                        }

                        if (b)
                        {
                            ne = new FamlDictionaryExpression(dict);
                            return true;
                        }
                        else
                        {
                            ne = node; return false;
                        }


                    case FamlExpressionType.FunctionCall:
                        FamlFunctionCallExpression mce = (FamlFunctionCallExpression)node;

                        FamlExpression[] newargs = new FamlExpression[mce.Arguments.Count];
                        i = 0;
                        b = false;
                        foreach (FamlExpression arg in mce.Arguments)
                        {
                            if (arg.Metadata.IsIndependent)
                            {
                                newargs[i] = GetStub(arg);
                                b = b || newargs[i] != arg;
                                i++;
                            }
                            else
                            {
                                b = Visit(arg, out newargs[i++]) || b;
                            }
                        }

                        if (b)
                        {
                            ne = new FamlFunctionCallExpression(mce.Operation, newargs);
                            return true;
                        }
                        else
                        {
                            ne = node;
                            return false;
                        }                        
                    
                    default:
                        break;
                }
             
            } /* eof case when I am dependent */

            switch (node.NodeType)
            {
                case FamlExpressionType.ConditionalExpression:
                    FamlConditionalExpression fcnde = (FamlConditionalExpression)node;
                    FamlExpression cond, trueBr, falseBr;
                    
                    b = Visit(fcnde.Condition, out cond);
                    b = Visit(fcnde.TrueBranch, out trueBr) || b;
                    b = Visit(fcnde.FalseBranch, out falseBr) || b;
                  
                    if (b)
                    {
                        ne = new FamlConditionalExpression(cond, trueBr, falseBr);
                        return true;
                    }
                    break;

                case FamlExpressionType.Dictionary:
                    FamlDictionaryExpression fde = (FamlDictionaryExpression)node;

                    string[] keys = fde.GetKeys();
                    OrderedDictionary dict = new OrderedDictionary(keys.Length);

                    b = false;
                    FamlExpression exp;
                    for (i = 0; i < keys.Length; i++)
                    {
                        b = Visit(fde.GetField(keys[i]), out exp) || b;
                        dict.Add(keys[i], exp);
                    }

                    if (b)
                    {
                        ne = new FamlDictionaryExpression(dict);
                        return true;
                    }
                    break;

                case FamlExpressionType.FieldAccess:
                    FamlFieldExpression me = (FamlFieldExpression)node;

                    FamlExpression expr;
                    if (Visit(me.Target, out expr))
                    {
                        ne = new FamlFieldExpression(expr, me.Field);
                        return true;
                    }
                    break;


                case FamlExpressionType.UnaryExpression:
                    FamlUnaryExpression ue = (FamlUnaryExpression)node;

                    FamlExpression operand;
                    if (Visit(ue.Target, out operand))
                    {
                        if (ue.Operator == UnaryOperations.Quote)
                            ne = operand;
                        else
                            ne = new FamlUnaryExpression(ue.Operator, operand);
                        return true;
                    }
                    break;


                case FamlExpressionType.BinaryExpression:
                    FamlBinaryExpression be = (FamlBinaryExpression)node;

                    b = false;

                    FamlExpression operandLeft;
                    FamlExpression operandRight;

                    b = Visit(be.Left, out operandLeft);
                    flag = Visit(be.Right, out operandRight);
                    b = flag || b;

                    if (b)
                    {
                        ne = new FamlBinaryExpression(operandLeft, operandRight, be.Operator); 
                        return true;
                    }
                    break;


                case FamlExpressionType.NewArray:
                    FamlInlineArrayExpression nae = (FamlInlineArrayExpression)node;

                    if (nae.Items.Count == 0)
                    {
                        ne = nae;
                        return false;
                    }

                    FamlExpression[] items = new FamlExpression[nae.Items.Count];
                    i = 0;
                    b = false;
                    foreach (FamlExpression arg in nae.Items)
                    {
                        flag = Visit(arg, out items[i++]);
                        b = flag || b;
                    }

                    if (b)
                    {
                        ne = new FamlInlineArrayExpression(items);
                        return true;
                    }
                    break;
                

                case FamlExpressionType.Lambda:
                    FamlLambdaExpression lexp = (FamlLambdaExpression)node;

                    FamlExpression body;
                    if (Visit(lexp.Body, out body))
                    {
                        ne = new FamlLambdaExpression(body, lexp.Parameters);
                        return true;
                    }
                    break;


                case FamlExpressionType.FunctionCall:
                    FamlFunctionCallExpression mce = (FamlFunctionCallExpression)node;

                    FamlExpression[] newargs = new FamlExpression[mce.Arguments.Count];
                    i = 0;
                    b = false;
                    foreach (FamlExpression arg in mce.Arguments)
                    {
                        flag = Visit(arg, out newargs[i++]);
                        b = flag || b;
                    }

                    if (b)
                    {
                        ne = new FamlFunctionCallExpression(mce.Operation, newargs); 
                        return true;
                    }
                    break;
            }

            ne = node;
            return false;
        }

        /// <summary>
        /// Returns the same expression if stub is not required
        /// </summary>
        /// <param name="exp"></param>
        /// <returns></returns>
        private FamlExpression GetStub(FamlExpression exp)
        {
            if (exp.NodeType == FamlExpressionType.Constant)
                return exp;
            if (exp.NodeType == FamlExpressionType.ExpressionReference)
                return exp;
            if (exp.NodeType == FamlExpressionType.Lambda)
                return exp;

            FamlExpression ne;
            Visit(exp, out ne);

            FamlReferenceExpression famlRef = new FamlReferenceExpression(ne);
            sequence.AddLast(famlRef.ReferencedContainer);

            return famlRef;
        }
    }
}
